package com.xy.service.system;

import com.alibaba.fastjson2.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xy.api.constant.RedisConstant;
import com.xy.api.exception.BizException;
import com.xy.api.model.dto.system.MenuDTO;
import com.xy.api.model.dto.system.MenuMetaDTO;
import com.xy.api.model.dto.system.UserInfoDTO;
import com.xy.api.model.response.Rows;
import com.xy.api.service.system.IMenuService;
import com.xy.auth.UserContext;
import com.xy.mapper.system.MenuMapper;
import com.xy.mapper.system.MenuMetaMapper;
import com.xy.mapper.system.MenuRoleMapper;
import com.xy.model.po.system.MenuMetaPO;
import com.xy.model.po.system.MenuPO;
import com.xy.model.po.system.MenuRolePO;
import com.xy.utils.CustomBeanUtil;
import com.xy.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class MenuServiceImpl implements IMenuService {

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private MenuMetaMapper menuMetaMapper;

    @Autowired
    private MenuRoleMapper menuRoleMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    public Rows<MenuDTO> queryMenuList() {
        //todo 加一下缓存
        Rows<MenuDTO> result = new Rows<>();
        LambdaQueryWrapper<MenuPO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                .eq(MenuPO::getIsDeleted, false)
                .orderByAsc(MenuPO::getSequence);
        List<MenuPO> menuPOS = menuMapper.selectList(queryWrapper);
        List<MenuDTO> menuList = CustomBeanUtil.copyListProperties(menuPOS, MenuDTO::new);
        fillMenuMeta(menuList);
        fillMenuRole(menuList);

        //构建菜单的树形结构
        List<MenuDTO> levelFirst = buildMenuTree(menuList);

        result.setRows(levelFirst);
        result.setTotal(levelFirst.size());
        return result;
    }

    private List<MenuDTO> buildMenuTree(List<MenuDTO> menuList) {
        if (CollectionUtils.isEmpty(menuList)) {
            return new ArrayList<>();
        }
        Map<Long, MenuDTO> menuMap = menuList.stream().collect(Collectors.toMap(e -> e.getId(), e -> e));
        List<MenuDTO> levelFirst = new ArrayList<>();

        for (MenuDTO menuDTO : menuList) {
            Long parentId = menuDTO.getParentId();
            if (menuMap.containsKey(parentId)) {
                MenuDTO parentMenu = menuMap.get(parentId);
                if (null == parentMenu.getChildren()) {
                    List<MenuDTO> children = new ArrayList<>();
                    children.add(menuDTO);
                    menuMap.get(parentId).setChildren(children);
                } else {
                    menuMap.get(parentId).getChildren().add(menuDTO);
                }
            } else {
                levelFirst.add(menuDTO);
            }
        }
        boolean pass = checkCircle(new HashSet<>(), levelFirst);
        if (!pass) {
            throw new BizException("菜单结构异常，请检查数据");
        }
        return levelFirst;
    }

    /**
     * 检测树结构正常（没有环）
     *
     * @param idSet
     * @param menuList
     */
    private boolean checkCircle(Set<Long> idSet, List<MenuDTO> menuList) {
        if (CollectionUtils.isEmpty(menuList)) {
            return true;
        }
        List<MenuDTO> nextList = new ArrayList<>();
        for (MenuDTO menu : menuList) {
            if (idSet.contains(menu.getId())) {
                return false;
            }
            if (CollectionUtils.isEmpty(menu.getChildren())) {
                continue;
            }
            idSet.add(menu.getId());
            nextList.addAll(menu.getChildren());
        }

        return checkCircle(idSet, nextList);
    }

    /**
     * 补充menu Meta
     *
     * @param list
     */
    private void fillMenuMeta(List<MenuDTO> list) {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        List<Long> menuIdList = list.stream().map(e -> e.getId()).collect(Collectors.toList());
        LambdaQueryWrapper<MenuMetaPO> menuMateQuery = new LambdaQueryWrapper<>();
        menuMateQuery
                .eq(MenuMetaPO::getIsDeleted, 0)
                .in(MenuMetaPO::getMenuId, menuIdList);
        List<MenuMetaPO> menuMetaPOS = menuMetaMapper.selectList(menuMateQuery);
        List<MenuMetaDTO> menuMetaList = CustomBeanUtil.copyListProperties(menuMetaPOS, MenuMetaDTO::new);
        if (!CollectionUtils.isEmpty(menuMetaList)) {
            Map<Long, MenuMetaDTO> menuMetaMap = menuMetaList.stream().collect(Collectors.toMap(e -> e.getMenuId(), e -> e, (e1, e2) -> e1));
            for (MenuDTO menuDTO : list) {
                Long menuId = menuDTO.getId();
                if (menuMetaMap.containsKey(menuId)) {
                    menuDTO.setMeta(menuMetaMap.get(menuId));
                }
            }
        }
    }

    /**
     * 补充菜单关联的角色信息
     *
     * @param list
     */
    private void fillMenuRole(List<MenuDTO> list) {
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        LambdaQueryWrapper<MenuRolePO> menuRoleQuery = new LambdaQueryWrapper<>();
        menuRoleQuery.eq(MenuRolePO::getIsDeleted, false);
        List<MenuRolePO> menuRolePOS = menuRoleMapper.selectList(menuRoleQuery);
        Map<Long, List<MenuRolePO>> menuRoleMap = menuRolePOS.stream().collect(Collectors.groupingBy(e -> e.getMenuId()));
        for (MenuDTO menuDTO : list) {
            if (menuRoleMap.containsKey(menuDTO.getId())) {
                List<String> roleCodeList = menuRoleMap.get(menuDTO.getId()).stream().map(e -> e.getRoleCode()).collect(Collectors.toList());
                menuDTO.setRoleCodeList(roleCodeList);
            }
        }
    }

    /**
     * 补充menu Meta
     *
     * @param menu
     */
    private void fillMenuMeta(MenuDTO menu) {
        if (null == menu || null == menu.getId()) {
            return;
        }
        LambdaQueryWrapper<MenuMetaPO> menuMateQuery = new LambdaQueryWrapper<>();
        menuMateQuery
                .eq(MenuMetaPO::getIsDeleted, 0)
                .eq(MenuMetaPO::getMenuId, menu.getId());
        MenuMetaPO menuMetaPO = menuMetaMapper.selectOne(menuMateQuery);
        MenuMetaDTO menuMeta = new MenuMetaDTO();
        CustomBeanUtil.copyProperties(menuMetaPO, menuMeta);
        menu.setMeta(menuMeta);
    }

    /**
     * 获取当前用户的角色（使用'#'连接）
     *
     * @return
     */
    private String getRoleAuthMenuListKey() {
        UserInfoDTO currentUserInfo = UserContext.get();
        List<String> roleCodeList = currentUserInfo.getRoleCodeList();
        if (CollectionUtils.isEmpty(roleCodeList)) {
            throw new BizException("当前用户未分配角色");
        }
        roleCodeList.sort(new Comparator<String>() {
            @Override
            public int compare(String s, String t1) {
                return s.compareTo(t1);
            }
        });

        StringBuilder sb = new StringBuilder();
        for (String roleCode : roleCodeList) {
            sb.append(roleCode + "#");
        }
        String menusKey = String.format(RedisConstant.MENUS_ROLE_CODE, sb);
        return menusKey;
    }

    @Override
    public Rows<MenuDTO> authMenuList() {
        List<String> roleCodeList = UserContext.get().getRoleCodeList();
        Rows<MenuDTO> result = new Rows<>();
        String menusKey = getRoleAuthMenuListKey();
        String values = redisUtil.get(menusKey);
        List<MenuDTO> levelFirst = null;
        if (StringUtils.hasText(values)) {
            levelFirst = JSONArray.parseArray(values, MenuDTO.class);
        } else {
            List<MenuPO> menuPOS = menuMapper.queryMenuByRoleCodes(roleCodeList);
            List<MenuDTO> menuList = CustomBeanUtil.copyListProperties(menuPOS, MenuDTO::new);
            fillMenuMeta(menuList);
            fillMenuRole(menuList);

            //构建菜单的树形结构
            levelFirst = buildMenuTree(menuList);
            redisUtil.set(menusKey, JSONArray.toJSONString(levelFirst), Duration.ofHours(2));
        }

        result.setRows(levelFirst);
        result.setTotal(levelFirst.size());
        return result;
    }

    @Override
    public MenuDTO queryMenuDetail(Long id) {
        MenuDTO menu = new MenuDTO();
        MenuPO menuPO = menuMapper.selectById(id);
        CustomBeanUtil.copyProperties(menuPO, menu);
        fillMenuMeta(menu);
        return menu;
    }

    @Override
    public void editMenu(MenuDTO request) {
        //检查上级菜单是否会形成环
        if (null == request.getParentId()) {
            //t_menu#id=1,表示的是‘/‘
            request.setParentId(1l);
        }
        if (null != request.getId()) {
            checkCircle(request.getId(), request.getParentId());
            //更新
            updateMenu(request);
        } else {
            //新增
            addMenu(request);
        }
        String menusKey = getRoleAuthMenuListKey();
        redisUtil.delete(menusKey);
    }

    /**
     * 检查上级菜单是否会形成环
     *
     * @param currentMenuId 当前菜单ID
     * @param parentMenuId  父菜单ID
     */
    private void checkCircle(Long currentMenuId, Long parentMenuId) {
        if (null == parentMenuId) {
            return;
        }
        LambdaQueryWrapper<MenuPO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(MenuPO::getIsDeleted, false);
        List<MenuPO> menuPOS = menuMapper.selectList(queryWrapper);
        Map<Long, MenuPO> menuMap = menuPOS.stream().collect(Collectors.toMap(e -> e.getId(), e -> e));
        Set<Long> menuIdSet = new HashSet<>();
        menuIdSet.add(currentMenuId);
        MenuPO parentNode = menuMap.get(parentMenuId);
        while (true) {
            if (menuIdSet.contains(parentNode.getId())) {
                throw new BizException("存在环");
            }
            menuIdSet.add(parentNode.getId());
            Long parentId = parentNode.getParentId();
            if (!menuMap.containsKey(parentId)) {
                break;
            }
            parentNode = menuMap.get(parentId);
        }
    }

    private void addMenu(MenuDTO menu) {
        MenuPO addMenuRecord = new MenuPO();
        CustomBeanUtil.copyProperties(menu, addMenuRecord);
        menuMapper.insert(addMenuRecord);

        MenuMetaPO addMenuMetaRecord = new MenuMetaPO();
        CustomBeanUtil.copyProperties(menu.getMeta(), addMenuMetaRecord);
        addMenuMetaRecord.setMenuId(addMenuRecord.getId());
        menuMetaMapper.insert(addMenuMetaRecord);

        Long menuId = addMenuRecord.getId();
        if (!CollectionUtils.isEmpty(menu.getRoleCodeList())) {
            for (String roleCode : menu.getRoleCodeList()) {
                MenuRolePO menuRoleRecord = new MenuRolePO();
                menuRoleRecord.setMenuId(menuId);
                menuRoleRecord.setRoleCode(roleCode);
                menuRoleMapper.insert(menuRoleRecord);
            }
        }
    }

    private void updateMenu(MenuDTO menu) {
        Assert.notNull(menu.getId(), "菜单ID非空");

        MenuPO updateMenuRecord = new MenuPO();
        CustomBeanUtil.copyProperties(menu, updateMenuRecord);
        menuMapper.updateById(updateMenuRecord);

        MenuMetaPO updateMenuMetaRecord = new MenuMetaPO();
        CustomBeanUtil.copyProperties(menu.getMeta(), updateMenuMetaRecord);
        LambdaQueryWrapper<MenuMetaPO> menuRoleQuery = new LambdaQueryWrapper<>();
        menuRoleQuery.eq(MenuMetaPO::getMenuId, menu.getId());
        menuMetaMapper.update(updateMenuMetaRecord, menuRoleQuery);

        LambdaQueryWrapper<MenuRolePO> deleteMenuRoleQuery = new LambdaQueryWrapper<>();
        deleteMenuRoleQuery.eq(MenuRolePO::getMenuId, menu.getId());
        MenuRolePO deleteMenuRoleRecord = new MenuRolePO();
        deleteMenuRoleRecord.setIsDeleted(true);
        menuRoleMapper.update(deleteMenuRoleRecord, deleteMenuRoleQuery);

        Long menuId = menu.getId();
        if (!CollectionUtils.isEmpty(menu.getRoleCodeList())) {
            for (String roleCode : menu.getRoleCodeList()) {
                MenuRolePO menuRoleRecord = new MenuRolePO();
                menuRoleRecord.setMenuId(menuId);
                menuRoleRecord.setRoleCode(roleCode);
                menuRoleMapper.insert(menuRoleRecord);
            }
        }
    }

    @Override
    public void deleteMenu(Long id) {
        Assert.notNull(id, "菜单ID必填");
        MenuPO delRecord = new MenuPO();
        delRecord.setIsDeleted(true);
        delRecord.setId(id);
        menuMapper.updateById(delRecord);

        String menusKey = getRoleAuthMenuListKey();
        redisUtil.delete(menusKey);
    }
}
